#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <pthread.h>

#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>

#define NUM_ROUTINE 20
#define N 6

typedef struct Var_t              /*  Variables sent to threads   */
{
    char    m_buf[200];
    int     m_semid;
}Var;

/*  union for semaphore */
union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };

void* WriteThread(void* _var)
{
    struct sembuf   sop                     ;
    int             semid                   ;
    int             i       = 0             ;
    int             n       = NUM_ROUTINE   ;
    char            cur = 'a';

    assert(_var);

    semid = ((Var*)_var)->m_semid;


    while(n)
    {
        sop.sem_num = 0;
        sop.sem_flg = 0;
        sop.sem_op = -N;                        /*  decrement semaphore 0 by N                            */
        semop(semid, &sop, 1);

        puts("WriteThread: out of lock");
        /*strcpy((((Var*)_var)->m_buf), "aaaaaaaa");*/
        for(i = 0; i < 20; ++i)
        {
            *(((Var*)_var)->m_buf + i)  = cur;
        }
        *(((Var*)_var)->m_buf + i) = '\0';
        cur = (1 + cur)%255 ;


        sop.sem_num = 2;
        sop.sem_flg = 0;
        sop.sem_op = -N;                        /*  decrement semaphore 2 by N                            */
        semop(semid, &sop, 1);

        sop.sem_num = 1;
        sop.sem_flg = 0;
        sop.sem_op = N;                        /*  increment semaphore 1 by N                            */
        semop(semid, &sop, 1);

        --n;
    }

    pthread_exit(0);

    return 0;
}

void* ReadThread(void* _var)
{
    struct sembuf   sop;
    int semid;
    int             n       = NUM_ROUTINE   ;

    assert(_var);

    semid = ((Var*)_var)->m_semid;

    /*  semaphore 0 => prevents writing before all already read.
        semaphore 1 => prevents reading before already written.
        semaphore 2 => prevents thread that read going into reading again.  */
    while(n)
    {
        sop.sem_num = 1;
        sop.sem_flg = 0;
        sop.sem_op = -1;                        /*  decrement semaphore 1 by 1 (possible after write incremented by N)  */        semop(semid, &sop, 1);

        printf("%s\n", ((Var*)_var)->m_buf);

        sop.sem_num = 2;
        sop.sem_flg = 0;
        sop.sem_op = 1;                        /*  increment semaphore2 by 1 (it's the read thread counter)   */
        semop(semid, &sop, 1);

        sop.sem_num = 2;
        sop.sem_flg = 0;
        sop.sem_op  = -N;                        /*  decrement semaphore 2 by N (possible if all threads already read) */
        semop(semid, &sop, 1);

        sop.sem_num = 2;
        sop.sem_flg = 0;
        sop.sem_op = N;                        /*  increment semaphore 2 by N (for other threads to continue)   */
        semop(semid, &sop, 1);

        sop.sem_num = 0;
        sop.sem_flg = 0;
        sop.sem_op  = 1;                        /*  increment semaphore 0 by 1  (after all threads do that the write can continue   */
        semop(semid, &sop, 1);

        --n;
    }

    pthread_exit(0);

    return 0;
}


int main()
{
    key_t       key                 ;
    FILE*       fp                  ;
    char        path[] = "/tmp/owmp";
    Var         v                   ;
    int         i                   ;
    pthread_t   threads[N+1]        ;

    if((fp = fopen(path,"w")) == 0)
    {
        perror("fopen");
    }

    key = ftok(path, 'A');
    if(key == -1)
    {
        perror("ftok for semaphore");
    }

    v.m_semid = semget( key  ,                          /*  unique key to semaphore             */
                    3    ,                          /*  number of semaphores in the set     */
                    0644 | IPC_CREAT| IPC_EXCL);   /* Flags of permissions and creation    */
    if(v.m_semid == -1)
    {
        perror("semget");
    }


    if(semctl(v.m_semid, 0, SETVAL, N) == -1) /*  Init semaphore 0 with the value N    */
    {
        perror("init semaphore");
        return 0;
    }

    if(semctl(v.m_semid, 1, SETVAL, 0) == -1) /*  Init semaphore 1 with the value 0    */
    {
        perror("init semaphore");
        return 0;
    }

    if(semctl(v.m_semid, 2, SETVAL, N) == -1) /*  Init semaphore 2 with the value N    */
    {
        perror("init semaphore");
        return 0;
    }

    if(pthread_create(&threads[0],      /*  store the ID of the created thread  */
                          0,            /*  thread attributes                   */
                          WriteThread , /*  start routine                       */
                          (void*)(&v))) /*  arguments for start routine         */
        {
           fprintf(stderr, "error in creating thread\n");
           return 1;
        }

    for(i = 1; i < N+1; ++i)
    {
        if(pthread_create(&threads[i], 0, ReadThread , (void*)(&v)) )
        {
           fprintf(stderr, "error in creating thread\n");
           return 1;
        }
    }

    for(i = 0; i < N+1; ++i)
    {
        pthread_join(threads[i], 0);
    }


    if(semctl(v.m_semid, IPC_RMID, 0))
    {
        perror("semctl");
        return 1;
    }

    return 0;
}
